﻿using FolderPickerLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using yCloudDesktop.YCloudService;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;

namespace yCloudDesktop
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        List<String> intervalOptions;
        private string rootPath;
        private yCloudClientEntities entities;
        private YCloudServiceClient service;
        private FileSystemWatcher directoriesWatcher, filesWatcher;
        private int refreshRate;
        private NotifyIcon notifyIcon;

        public MainWindow()
        {
            InitializeComponent();
            this.entities = new yCloudClientEntities();
            this.service = new YCloudServiceClient();
            notifyIcon = new System.Windows.Forms.NotifyIcon();
            notifyIcon.Text = "yCloud";
            notifyIcon.Icon = new System.Drawing.Icon("yCloud.ico");
            this.ShowTrayIcon(true);
            string homePath = Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
            rootPath = homePath + "\\yCloud";
            if (!Directory.Exists(rootPath))
            {
                Directory.CreateDirectory(rootPath);
            }
            StartMonitoringRoot();
            StartRefreshing(30);
        }

        private void StartRefreshing(int rateInSeconds)
        {
            this.refreshRate = rateInSeconds * 1000;
            if (rateInSeconds != -1)
            {
                Thread refreshThread = new Thread(RefreshService);
                refreshThread.Start();
            }
        }

        private void RefreshService()
        {
            while (refreshRate != -1)
            {
                this.SynchWithService();
                Thread.Sleep(refreshRate);
            }
        }

        private void SynchWithService()
        {
            lock (service)
            {
                //We need to stop listenning for directory changes, so that if the synching process by any chances adds or removes an item, we don't get notified. We want to get notified only if a directory changes from the file system by the user, not ourselves.
                this.PauseMonitoringRoot();
                SerializableFolder root = service.GetFolder(0);
                SynchWithFileSystem(root);
                this.ResumeMonitoringRoot();
            }
        }

        private void SynchWithFileSystem(SerializableFolder folder)
        {
            Item inFolder = entities.Items.Where(f => f.ID == folder.Id).FirstOrDefault();
            if (inFolder == null)
            {
                inFolder = new Item();
                inFolder.Path = "";
            }
            //let's remove the ones we have locally, but that have been removed from the cloud
            string inFolderPath = inFolder.Path;
            List<int> allLocalItemsInFolder = new List<int>();
            foreach (Item item in entities.Items)
            {
                if (item != inFolder)
                {
                    string itemName = item.Path.Split('\\').LastOrDefault();
                    string itemPathNoName = item.Path.Length > itemName.Length ? item.Path.Substring(0, item.Path.Length - itemName.Length - 1) : "";
                    if ((itemPathNoName == inFolderPath) || (inFolderPath == "" && itemPathNoName == ""))
                    {
                        allLocalItemsInFolder.Add(item.ID);
                    }
                }
            }
            foreach (int localItemId in allLocalItemsInFolder)
            {
                bool itemExistsInCloud = folder.Files.Contains(localItemId) || folder.SubFolders.Contains(localItemId);
                if (!itemExistsInCloud)
                {
                    Item localItem = entities.Items.Where(i => i.ID == localItemId).FirstOrDefault();
                    string itemToDeletePath = rootPath + "\\" + localItem.Path;
                    if (Directory.Exists(itemToDeletePath))
                        Directory.Delete(itemToDeletePath, true);
                    else if (File.Exists(itemToDeletePath))
                        File.Delete(itemToDeletePath);
                    //Now from the database
                    entities.Items.DeleteObject(localItem);
                    entities.SaveChanges();
                    notifyIcon.ShowBalloonTip(2000, "Item Removed", localItem.Path + " Deleted", ToolTipIcon.Info);
                }
            }
            foreach (int fileId in folder.Files)
            {
                Item localFile = entities.Items.Where(i => i.ID == fileId).FirstOrDefault();
                if (localFile == null)
                {
                    //So apparently this is a new file that has been up in the service for a while but I haven't had a chance to pull down locally
                    SerializableFile newFile = service.GetFile(fileId);
                    File.WriteAllBytes(rootPath + "\\" + (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + newFile.Name, newFile.Content);
                    //We have pushed the new file the file system, now let's add it to our local database
                    Item newFileItem = new Item();
                    newFileItem.ID = newFile.ID;
                    newFileItem.Path = (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + newFile.Name;
                    entities.Items.AddObject(newFileItem);
                    entities.SaveChanges();
                    notifyIcon.ShowBalloonTip(2000, "New File Downloaded", newFile.Name + " downloaded", ToolTipIcon.Info);
                }
                else
                {
                    //we do have a file locally that matches the id from the service, but let's see if the name matches
                    SerializableFile fileInService = service.GetFileWithoutData(fileId);
                    string localFileName = localFile.Path.Split('\\').Last();
                    if (localFileName != fileInService.Name)
                    {
                        //HOLY JESUS!!! Looks like the name of the file in the cloud changed and I just now I found out about it. I need to update myself
                        File.Move(rootPath + "\\" + localFile.Path, rootPath + "\\" + (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + fileInService.Name);
                        //Renaming has been done in the file system. Now to my local database
                        localFile.Path = (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + fileInService.Name;
                        entities.SaveChanges();
                    }
                }
            }
            foreach (int folderId in folder.SubFolders)
            {
                Item localFolder = entities.Items.Where(i => i.ID == folderId).FirstOrDefault();
                SerializableFolder folderInCloud = service.GetFolder(folderId);
                if (localFolder == null)
                {
                    //Just like the files, it looks like there is a new folder in the cloud that I need to make locally
                    Directory.CreateDirectory(rootPath + "\\" + (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + folderInCloud.Name);
                    //Add it to our database
                    Item newLocalFolder = new Item();
                    newLocalFolder.ID = folderId;
                    newLocalFolder.Path = (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + folderInCloud.Name;
                    entities.Items.AddObject(newLocalFolder);
                    entities.SaveChanges();
                    notifyIcon.ShowBalloonTip(2000, "New Folder Downloaded", folderInCloud.Name + " downloaded", ToolTipIcon.Info);
                }
                else
                {
                    //We do have that folder now, but we need to check if its name changed
                    string localFolderName = localFolder.Path.Split('\\').Last();
                    if (localFolderName != folderInCloud.Name)
                    {
                        //Name changed, so we need to update locally
                        Directory.Move(rootPath + "\\" + (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + localFolderName, rootPath + "\\" + (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + folderInCloud.Name);
                        //Now in our local database
                        localFolder.Path = (string.IsNullOrEmpty(inFolder.Path) ? "" : inFolder.Path + "\\") + folderInCloud.Name;
                        entities.SaveChanges();
                    }
                }
                //Get inside that folder and pull down/update the stuff that it has from the service
                SynchWithFileSystem(folderInCloud);
            }
        }

        private void StartMonitoringRoot()
        {
            SetUpDirectoryWatcher();
            SetupFilesWatcher();
            ResumeMonitoringRoot();
        }

        private void SetupFilesWatcher()
        {
            filesWatcher = new FileSystemWatcher(rootPath);
            filesWatcher.IncludeSubdirectories = true;
            filesWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
            filesWatcher.Created += ItemAddedToDirectory;
            filesWatcher.Deleted += ItemDeletedFromDirectory;
            filesWatcher.Changed += FileChangedInDirectory;
            filesWatcher.Renamed += FileRenamedInDirectory;
        }

        private void FileRenamedInDirectory(object sender, FileSystemEventArgs e)
        {
            RenamedEventArgs renamedEvent = (RenamedEventArgs)e;
            Item file = entities.Items.Where(i => i.Path == renamedEvent.OldName).FirstOrDefault();
            string newName = renamedEvent.Name.Split('\\').LastOrDefault();
            service.UpdateFileInfo(file.ID, newName);
            file.Path = renamedEvent.Name;
            entities.SaveChanges();
        }

        private void FileChangedInDirectory(object sender, FileSystemEventArgs e)
        {
            if (File.Exists(e.FullPath))
            {
                byte[] newBytes = this.ReadFile(e.FullPath);
                Item file = entities.Items.Where(i => i.Path == e.Name).FirstOrDefault();
                string fileName = file.Path.Split('\\').LastOrDefault();
                string parentName = file.Path.Length > fileName.Length ? file.Path.Substring(0, file.Path.Length - fileName.Length - 1) : "";
                int parentId = parentName == "" ? 0 : entities.Items.Where(i => i.Path == parentName).FirstOrDefault().ID;
                service.DeleteFile(file.ID);
                int newId = service.UploadFile(newBytes, fileName, parentId);
                Item newFile = new Item();
                newFile.ID = newId;
                newFile.Path = file.Path;
                entities.Items.DeleteObject(file);
                entities.Items.AddObject(newFile);
                entities.SaveChanges();
            }
        }

        private void DirectoryRenamed(object sender, RenamedEventArgs e)
        {
            string oldPath = e.OldName;
            Item directory = entities.Items.Where(i => i.Path == oldPath).FirstOrDefault();
            string newName = e.Name.Split('\\').LastOrDefault();
            service.RenameFolder(directory.ID, newName);
            directory.Path = e.Name;
            entities.SaveChanges();
        }

        private void SetUpDirectoryWatcher()
        {
            directoriesWatcher = new FileSystemWatcher(rootPath);
            directoriesWatcher.IncludeSubdirectories = true;
            directoriesWatcher.NotifyFilter = NotifyFilters.DirectoryName;
            directoriesWatcher.Created += ItemAddedToDirectory;
            directoriesWatcher.Deleted += ItemDeletedFromDirectory;
            directoriesWatcher.Renamed += DirectoryRenamed;
        }

        private void ResumeMonitoringRoot()
        {
            filesWatcher.EnableRaisingEvents = true;
            directoriesWatcher.EnableRaisingEvents = true;
        }

        private void PauseMonitoringRoot()
        {
            filesWatcher.EnableRaisingEvents = false;
            directoriesWatcher.EnableRaisingEvents = false;
        }

        private void ItemDeletedFromDirectory(object sender, FileSystemEventArgs e)
        {
            lock (service)
            {
                string fullPath = e.FullPath;
                string itemName = fullPath.Split('\\').Last();
                string pathAfterRoot = e.Name;
                string folderPath = pathAfterRoot.Substring(0, pathAfterRoot.Length - itemName.Length);
                Item item = entities.Items.Where(i => i.Path == pathAfterRoot).FirstOrDefault();
                Item[] itemsInside = entities.Items.Where(i => i.Path.StartsWith(pathAfterRoot) && entities.Items.Where(it => it.Path == pathAfterRoot).FirstOrDefault() != i).ToArray();
                int itemId = item == null ? 0 : item.ID;
                bool deletingFolder = sender == directoriesWatcher;
                if (deletingFolder)
                {
                    service.DeleteFolder(itemId);
                    foreach (Item i in itemsInside)
                    {
                        entities.Items.DeleteObject(i);
                    }
                }
                else
                    service.DeleteFile(itemId);
                entities.Items.DeleteObject(item);
                entities.SaveChanges();
            }
        }

        private void ItemAddedToDirectory(object sender, FileSystemEventArgs e)
        {
            lock (service)
            {
                string fullPath = e.FullPath;
                string itemName = fullPath.Split('\\').Last();
                string pathAfterRoot = e.Name;
                string folderPath = pathAfterRoot.Substring(0, pathAfterRoot.Length - itemName.Length);
                Item folder = null;
                folder = entities.Items.Where(i => i.Path + "\\" == folderPath).FirstOrDefault();
                int folderId = folder == null ? 0 : folder.ID;
                bool makingFolder = (File.GetAttributes(fullPath) & FileAttributes.Directory) == FileAttributes.Directory;
                int id = 0;
                if (makingFolder)
                {
                    id = service.MakeFolder(itemName, folderId);
                }
                else
                {
                    byte[] bytes = ReadFile(fullPath);
                    id = service.UploadFile(bytes, itemName, folderId);
                }
                Item newFolder = new Item();
                newFolder.ID = id;
                newFolder.Path = pathAfterRoot;
                entities.AddToItems(newFolder);
                entities.SaveChanges();
            }
        }

        private byte[] ReadFile(string fullPath)
        {
            return File.ReadAllBytes(fullPath);
        }

        private void ShowTrayIcon(bool show)
        {
            if (notifyIcon != null)
                notifyIcon.Visible = show;
        }
    }
}
